import json
import os
import requests
from requests.adapters import HTTPAdapter
from requests.auth import HTTPBasicAuth
from urllib3.util.retry import Retry
import warnings
from typing import Dict, Any, Optional, Iterable, Tuple
import tempfile

"""
示例文件：演示使用 requests 库发送 HTTP GET / POST 的常见用法，覆盖工作中常见场景。
运行前：pip install requests urllib3
"""


# ---------------------------
# 基本 GET / POST 示例
# ---------------------------

def get_simple(url: str) -> requests.Response:
    """
    简单的 GET 请求。
    - 发送一个最常见的 GET 请求并返回 Response 对象。
    - 不设置超时、headers、params 等（生产环境应至少设置 timeout）。
    - 可能抛出 requests.RequestException（网络问题）或其他异常。

    参数:
      url: 目标 URL。

    返回:
      requests.Response 对象，包含 status_code、headers、text、json() 等。
    """
    resp = requests.get(url)
    return resp


def get_with_params(url: str, params: Dict[str, Any], headers: Optional[Dict[str, str]] = None, timeout: float = 10.0) -> requests.Response:
    """
    GET 请求带 query 参数、可选 headers 和 timeout。
    - params: 字典会被编码到 URL 查询字符串中，例如 {'q': 'python', 'page': 2}。
    - headers: 自定义请求头（如 User-Agent、Accept）。
    - timeout: 单位秒，既可以是单个 float（connect+read 的总超时），也可以是 (connect, read) 元组。

    返回:
      requests.Response。需要注意处理 resp.raise_for_status() 或检查 resp.status_code。
    """
    resp = requests.get(url, params=params, headers=headers, timeout=timeout)
    return resp


def get_with_session_and_retries(url: str, retries: int = 3, backoff_factor: float = 0.3, status_forcelist: Iterable[int] = (500, 502, 504)) -> requests.Response:
    """
    使用 Session + 重试策略来提高请求稳定性（适用于短暂服务端错误或网络波动）。
    - 使用 urllib3.util.retry.Retry 配置重试次数、退避因子和针对哪些状态码重试。
    - mount 到 Session 的 HTTPAdapter，适用于同一主机多次请求，节省连接开销。

    参数:
      url: 目标 URL。
      retries: 最大重试次数。
      backoff_factor: 退避因子，重试间隔 = backoff_factor * (2 ** (retry - 1))。
      status_forcelist: 针对这些响应码触发重试。

    注意:
      重试不会重放非幂等的请求体（例如带 body 的 POST）。如果需要对 POST 重试，需要小心幂等性。
    """
    session = requests.Session()
    retry = Retry(total=retries, backoff_factor=backoff_factor, status_forcelist=list(status_forcelist), allowed_methods=frozenset(['GET', 'HEAD', 'OPTIONS']))
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('https://', adapter)
    session.mount('http://', adapter)
    resp = session.get(url, timeout=10)
    session.close()
    return resp


def get_stream_download(url: str, dest_path: str, chunk_size: int = 1024 * 8):
    """
    流式下载大文件，避免一次性将响应体加载到内存中。
    - stream=True: 告诉 requests 不要立即读取响应体。
    - 使用 iter_content 分块写入文件，并可用于显示进度条。
    - 支持断点续传需结合 'Range' header（此处未实现）。

    参数:
      url: 下载 URL。
      dest_path: 保存本地文件路径。
      chunk_size: 每次读取的字节数。
    """
    with requests.get(url, stream=True, timeout=(5, 60)) as r:
        r.raise_for_status()
        with open(dest_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=chunk_size):
                if chunk:  # 过滤 keep-alive chunks
                    f.write(chunk)


# ---------------------------
# POST 请求常见用法
# ---------------------------

def post_json(url: str, json_data: Dict[str, Any], headers: Optional[Dict[str, str]] = None, timeout: float = 10.0) -> requests.Response:
    """
    发送 application/json 类型的 POST 请求（常用于 REST API）。
    - requests 提供 json= 参数，会自动序列化并设置 Content-Type: application/json。
    - 如果需要自定义序列化行为，可先调用 json.dumps()。

    参数:
      url: 目标 URL。
      json_data: 要发送的 JSON 可序列化对象。
      headers: 可附加的自定义 headers（注意不要覆盖 Content-Type，requests 会自动设置）。
      timeout: 超时。
    """
    resp = requests.post(url, json=json_data, headers=headers, timeout=timeout)
    return resp


def post_form(url: str, data: Dict[str, Any], headers: Optional[Dict[str, str]] = None, timeout: float = 10.0) -> requests.Response:
    """
    发送 application/x-www-form-urlencoded 表单数据（HTML 表单默认格式）。
    - 使用 data= 参数传递字典、元组或字符串。
    - 对于包含多个同名字段（如 tags=1&tags=2），可以使用 list of tuples 或 requests 自动编码。

    参数:
      url: 目标 URL。
      data: 表单字段字典或列表。
      headers: 可选自定义头。
    """
    resp = requests.post(url, data=data, headers=headers, timeout=timeout)
    return resp


def post_files(url: str, files: Dict[str, Tuple[str, bytes, Optional[str]]], data: Optional[Dict[str, Any]] = None, timeout: float = 60.0) -> requests.Response:
    """
    发送 multipart/form-data（文件上传）。
    - files 参数格式示例:
        files = {
            'filefield': ('filename.jpg', open('filename.jpg','rb'), 'image/jpeg')
        }
      也可以传入二进制 content 代替 file object。
    - data 可以同时携带普通表单字段。

    注意:
      - requests 会自动设置正确的 multipart 边界。
      - 如果文件很大，请确保以流式方式读取和发送（open(...,'rb')）。
    """
    # 示例传入 files 时通常使用 open(...)，调用方负责关闭文件或使用 with 语句。
    resp = requests.post(url, files=files, data=data, timeout=timeout)
    return resp


def post_with_auth_basic(url: str, username: str, password: str, json_data: Optional[Dict[str, Any]] = None) -> requests.Response:
    """
    使用 HTTP Basic Auth 发送请求（常见于内部服务或简易认证）。
    - requests 支持传入 auth=(username, password) 或 requests.auth.HTTPBasicAuth。
    - 如果使用 Session 可将 auth 设为默认值。

    参数:
      url: 目标 URL。
      username/password: 基本认证凭据。
      json_data: 可选的 JSON body。
    """
    resp = requests.post(url, auth=HTTPBasicAuth(username, password), json=json_data, timeout=10)
    return resp


def post_with_bearer_token(url: str, token: str, payload: Optional[Dict[str, Any]] = None) -> requests.Response:
    """
    使用 Bearer Token（OAuth2 常用）进行请求授权。
    - 在 header 中设置 Authorization: Bearer <token>。
    - 常用于调用受保护的 REST API。

    参数:
      url: 目标 URL。
      token: Bearer token 字符串。
      payload: 可以是 json/body。
    """
    headers = {'Authorization': f'Bearer {token}', 'Accept': 'application/json'}
    resp = requests.post(url, json=payload, headers=headers, timeout=10)
    return resp


# ---------------------------
# 进阶与实用工具
# ---------------------------

def request_with_proxy_and_cert(url: str, proxies: Dict[str, str] = None, cert: Optional[Tuple[str, str]] = None, verify: bool = True) -> requests.Response:
    """
    使用代理、客户端证书和自定义证书校验。
    - proxies: 例如 {'http': 'http://proxy.example:3128', 'https': 'http://proxy.example:3128'}
    - cert: 客户端证书 (cert_file, key_file) 或单一 pem 文件路径。
    - verify: 如果为 False，会跳过服务器证书校验（生产环境慎用）。可传入 CA bundle 路径以使用自定义 CA。

    返回:
      requests.Response
    """
    if not verify:
        # 关闭 InsecureRequestWarning
        warnings.simplefilter('ignore', requests.packages.urllib3.exceptions.InsecureRequestWarning)
    resp = requests.get(url, proxies=proxies, cert=cert, verify=verify, timeout=15)
    return resp


def parse_json_safe(response: requests.Response) -> Optional[Dict[str, Any]]:
    """
    安全解析 JSON 响应并处理常见错误。
    - 先检查 status_code，再尝试 response.json()，捕获 JSONDecodeError。
    - 返回字典或 None（解析失败）。
    """
    try:
        response.raise_for_status()
    except requests.HTTPError:
        # 记录或处理错误，示例中直接返回 None
        return None
    try:
        return response.json()
    except ValueError:
        # 非 JSON 响应
        return None


def prepare_and_send_custom_request(method: str, url: str, headers: Optional[Dict[str, str]] = None, params: Optional[Dict[str, Any]] = None, data: Optional[Any] = None, json_data: Optional[Any] = None, timeout: float = 10.0) -> requests.Response:
    """
    使用 PreparedRequest 进行更细粒度控制（例如签名、修改 body）。
    - 适合需要在发送前修改 request 对象或进行签名的场景。
    - 返回发送后的 Response。
    """
    session = requests.Session()
    req = requests.Request(method=method.upper(), url=url, headers=headers, params=params, data=data, json=json_data)
    prepared = session.prepare_request(req)
    # 可以在这里检查或修改 prepared.headers / body 等，之后发送
    resp = session.send(prepared, timeout=timeout)
    session.close()
    return resp


# ---------------------------
# 响应处理示例
# ---------------------------

def raise_for_status_example(url: str) -> Dict[str, Any]:
    """
    示例：检查响应状态并抛出异常（适合在需要非 2xx 报错时中断逻辑）。
    - response.raise_for_status() 会抛出 HTTPError，包含响应对象。
    - 可以在上层捕获以做统一错误处理或重试策略。
    """
    resp = requests.get(url, timeout=10)
    resp.raise_for_status()  # 如果状态码不是 2xx，将抛出 requests.exceptions.HTTPError
    return resp.json()


# ---------------------------
# 小结示例：会话与默认 headers
# ---------------------------

def create_session_with_default_headers(default_headers: Dict[str, str]) -> requests.Session:
    """
    创建一个具有默认 headers 的 Session，适用于多个请求使用统一 header（如 Authorization、User-Agent）。
    - 返回的 Session 应由调用方在不需要时关闭。
    """
    session = requests.Session()
    session.headers.update(default_headers)
    return session


# ---------------------------
# 示例用法（非单元测试）
# ---------------------------

if __name__ == "__main__":

    # GET 系列
    try:
        r_simple = get_simple("https://httpbin.org/get")
        print("get_simple:", r_simple.status_code)
    except Exception as e:
        print("get_simple failed:", e)

    try:
        r_params = get_with_params("https://httpbin.org/get", params={"q": "requests", "page": 1}, headers={"User-Agent": "demo/1.0"})
        print("get_with_params:", r_params.status_code)
    except Exception as e:
        print("get_with_params failed:", e)

    try:
        r_retries = get_with_session_and_retries("https://httpbin.org/status/200", retries=2)
        print("get_with_session_and_retries:", r_retries.status_code)
    except Exception as e:
        print("get_with_session_and_retries failed:", e)

    # 流式下载一个小文件到临时目录
    tmp_file = None
    try:
        tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
        tmp_file = tmp.name
        tmp.close()
        get_stream_download("https://httpbin.org/image/png", tmp_file)
        print("get_stream_download: saved to", tmp_file, "size", os.path.getsize(tmp_file))
    except Exception as e:
        print("get_stream_download failed:", e)
    finally:
        if tmp_file and os.path.exists(tmp_file):
            try:
                os.remove(tmp_file)
            except Exception:
                pass

    # POST 系列
    try:
        pj = post_json("https://httpbin.org/post", json_data={"name": "Alice", "age": 30})
        print("post_json:", pj.status_code)
    except Exception as e:
        print("post_json failed:", e)

    try:
        pf = post_form("https://httpbin.org/post", data={"field": "value", "num": 1})
        print("post_form:", pf.status_code)
    except Exception as e:
        print("post_form failed:", e)

    try:
        files = {"file": ("hello.txt", b"hello world", "text/plain")}
        pfile = post_files("https://httpbin.org/post", files=files, data={"meta": "x"})
        print("post_files:", pfile.status_code)
    except Exception as e:
        print("post_files failed:", e)

    try:
        pa = post_with_auth_basic("https://httpbin.org/post", "user", "pass", json_data={"auth": "test"})
        print("post_with_auth_basic:", pa.status_code)
    except Exception as e:
        print("post_with_auth_basic failed:", e)

    try:
        pb = post_with_bearer_token("https://httpbin.org/post", token="fake-token-123", payload={"x": 1})
        print("post_with_bearer_token:", pb.status_code)
    except Exception as e:
        print("post_with_bearer_token failed:", e)

    # 代理 / 证书 示例（不使用代理/证书）
    try:
        rp = request_with_proxy_and_cert("https://httpbin.org/get", proxies=None, cert=None, verify=True)
        print("request_with_proxy_and_cert:", rp.status_code)
    except Exception as e:
        print("request_with_proxy_and_cert failed:", e)

    # JSON 解析安全示例
    try:
        parsed = parse_json_safe(r_params)
        print("parse_json_safe (from get_with_params):", isinstance(parsed, dict))
    except Exception as e:
        print("parse_json_safe failed:", e)

    # PreparedRequest 示例
    try:
        prep = prepare_and_send_custom_request("GET", "https://httpbin.org/get", params={"from": "prepared"})
        print("prepare_and_send_custom_request:", prep.status_code)
    except Exception as e:
        print("prepare_and_send_custom_request failed:", e)

    # raise_for_status 示例（使用会返回 JSON 的 endpoint）
    try:
        rs = raise_for_status_example("https://httpbin.org/json")
        print("raise_for_status_example:", isinstance(rs, dict))
    except Exception as e:
        print("raise_for_status_example failed:", e)

    # Session with default headers
    try:
        s = create_session_with_default_headers({"User-Agent": "demo-session/1.0", "X-Demo": "1"})
        r_hdrs = s.get("https://httpbin.org/headers", timeout=5)
        print("create_session_with_default_headers:", r_hdrs.status_code)
        s.close()
    except Exception as e:
        print("create_session_with_default_headers failed:", e)